<?php
defined( '_JEXEC' ) or die( 'Restricted Access' );
/**
 * @version     $Id$
 * @package     Virtuemart
 * @subpackage  SemanticWeb Module
 * @copyright   Copyright (C) 2009 Alex Stolz - All rights reserved.
 * @license     GNU/GPL v2
 * SemanticWeb is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License 2
 * as published by the Free Software Foundation.
 
 * SemanticWeb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 
 * You should have received a copy of the GNU General Public License
 * along with SemanticWeb; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */


// include ARC2 framework
require_once(dirname(__FILE__).DS.'arc'.DS.'ARC2.php');

/**
 * Semantic Web controller
 *
 * core class of the application, where RDF-files are created
 *
 * @package    Virtuemart
 * @subpackage SemanticWeb Module
 * @link       http://code.google.com/p/goodrelations-for-joomla/
 * @since      Class available since Release 0.1
 */
class ModSemanticWebController {

	var $contentType = "application/xml";
	var $encoding = "utf-8";
	var $filename = "";
	var $special_validity = 0;
	var $offering_validity = 0;
	var $_timeout = 0;
	var $_params;
	var $object_properties = NULL;

	// check, if the virtuemart component is enabled
	function isVirtueMartEnabled() {
		$db =& JFactory::getDBO();
		$enabled = NULL;
		
		$db->setQuery("select enabled from #__components where ".$db->nameQuote( 'option' )."=".$db->quote( 'com_virtuemart' )." and parent='0'");
		if($row = $db->loadAssoc()) {
			$enabled = $row['enabled'];
		}
		
		if($enabled === NULL)
			return false;
		else
			return $enabled?true:false;
	}
	
	// set the OWL filename
	function setFilename($filename) {
		$this->filename = $filename;
	}
	
	// set a custom special validity duration
	function setSpecialValidity($special_validity) {
		$this->special_validity = $special_validity;
	}
	
	// set a custom offering validity duration
	function setOfferingValidity($offering_validity) {
		$this->offering_validity = $offering_validity;
	}
	
	// load the params from the backend
	function setParams($params) {
		$this->_params = $params;
	}
	
	// set the timeout in seconds before a cached version is refreshed
	function setTimeout($timeout) {
		$this->_timeout = $timeout;
	}
	
	// create w3c date format from any different time/date format
	function w3cDate($time, $days_offset=0) {
		$time = $time+$days_offset*86400;
		if ((int) PHP_VERSION >= 5)
			return date('c', $time);
		else {
			$offset = date('O', $time);
			return date('Y-m-d\TH:i:s', $time).substr($offset, 0, 3).':'.substr($offset, -2);
		}
	}
	
	// fetch all parameter names that are instances of an element (like eligibleRegions) in goodrelations
	function getAllParametersFromElement($search) {
		$db = new ps_DB;
		$db->query("select params from #__modules where module='mod_semanticweb'");
		if($db->next_record()) {
			$param_array = explode("\n", $db->f('params'));
			foreach($param_array as $equation) {
				list($param_name, $param_value) = explode("=", trim($equation));
				if(eregi($search, $param_name) && $param_name != '') { // key cannot be empty
					$param_key = ereg_replace('(_|'.$search.')', '', $param_name);
					if($param_value == '0') // skip radio buttons set to zero ('No')
						continue;
					else if($param_value == '1') { // one of the radio buttons ('Yes')
						if(($param_value = $this->_params->get($param_key."_duration")) != NULL)
							$param[$param_key] = $param_value; // assign duration of warranty in months
						else
							$param[$param_key] = true; // assign true
					}
					else { // a text field param
						$param[$param_key] = $param_value;
					}
				}
			}
			return $param;
		}
	}
	
	/**
	 * @since 1.4
	 * @access private
	 */
	function _redirect() {
		// attention, heavily-commented-out-area

		// maybe use this in addition to file time checking
		//Header("Expires: ".date("r",time()+$this->_timeout));

		/* no caching at all, doesn't seem to work as good:
		Header("Cache-Control: no-cache");
		Header("Pragma: no-cache");
		*/

		// HTTP redirect, some feed readers' simple HTTP implementations don't follow it
		//Header("Location: ".$filename);

		Header("Content-Type: ".$this->contentType."; charset=".$this->encoding."; filename=".basename($this->filename));
		Header("Content-Disposition: inline; filename=".basename($this->filename));
		readfile($this->filename, "r");
		die();
	}

	// generate a proper filename, if it doesn't exist
	function _generateFilename() {
		$fileInfo = pathinfo($_SERVER["PHP_SELF"]);
		return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".owl";
	}

	// save the owl file
	function saveRDF($displayContents=true) {
		if ($this->filename=="") {
			$this->filename = $this->_generateFilename();
		}
		$feedFile = fopen($this->filename, "w+");
		if ($feedFile) {
			// get RDF from _createRDF() and write content into file
			fputs($feedFile, $this->_createRDF());
			fclose($feedFile);
			if ($displayContents) {
				$this->_redirect(); // eventually redirect
			}

			return true;
		} else {
			echo "<br /><b>Error creating feed file, please check write permissions.</b><br />";
		}
	}
	
	/**
	 * Turns on caching and checks if there is a recent version of this feed in the cache.
	 * If there is, an HTTP redirect header is sent.
	 * To effectively use caching, you should create the FeedCreator object and call this method
	 * before anything else, especially before you do the time consuming task to build the feed
	 * (web fetching, for example).
	 * @since 1.4
	 */
	function tryUseCached() {
		//generate filename, if it doesn't exist a proper one
		if ($this->filename=="") {
			$this->filename = $this->_generateFilename();
		}
		
		// check existence and validity of the file
		if (file_exists($this->filename) && (time()-filemtime($this->filename) < $this->_timeout)) {
			return;
		}
		// no cached file available
		$this->saveRDF(false);
	}
	
	// create the main goodrelations file
	function _createRDF() {
		// namespace definition
		$namespaces = array(
			'base' =>  JURI::base() . $this->filename,
			'dc' => 'http://purl.org/dc/elements/1.1/',
			'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
			'xsd' => 'http://www.w3.org/2001/XMLSchema#',
			'owl' => 'http://www.w3.org/2002/07/owl#',
			'vcard' => 'http://www.w3.org/2006/vcard/ns#',
			'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
			'gr' => 'http://purl.org/goodrelations/v1#',
			'self' => JURI::base() . $this->filename . '#'
		);
		
		$elements = array(
			//'BusinessFunction', // not needed anymore, is directly saved as list param
			'BusinessEntityType',
			'DeliveryMethod',
			'PaymentMethod',
			//'eligibleRegions', // not needed, is directly saved in textfield param called eligibleRegions
			'WarrantyScope'
		);
		foreach($elements as $search) {
			$parameter_array_name = strtolower(ereg_replace('^_', '', ereg_replace('[A-Z]','_\\0',$search)));
			$$parameter_array_name = $this->getAllParametersFromElement($search); // get parameters from backend
		}
		
		// unit of measurement
		if(sizeof($this->getAllParametersFromElement('uom_weight'))>0)
		$uom_weight = array_pop($this->getAllParametersFromElement('uom_weight'));
		if(sizeof($this->getAllParametersFromElement('uom_lwh'))>0)
		$uom_lwh = array_pop($this->getAllParametersFromElement('uom_lwh'));
		
		// create document header
		foreach($namespaces as $ns => $uri) {
			$doctype .= "\n".'  <!ENTITY '.$ns.' "'.$uri.'" >';
			$xmlns .= "\n".'  xml'.($ns=='base'?'':'ns').':'.$ns.'="'.$uri.'"';
		}
		$xmlns .= "\n".'  xmlns="'.$namespaces['base'].'#"';
	
		$rdf = "<?xml version=\"1.0\" encoding=\"".$this->encoding."\"?>\n";
		
		$rdf .= "<!DOCTYPE rdf:RDF [$doctype]>\n\n";
		$rdf .= "<rdf:RDF $xmlns>\n\n";
		
		// some useful database instances
		$db = new ps_DB;
		$db2 = new ps_DB;
		$db3 = new ps_DB;
		$psp = new ps_product;
		// session specific variable auth (useful for calculate_price_including_tax, etc.)
		$auth = $_SESSION['auth'];
		
		$db->query("select vendor_id, vendor_name, contact_title, contact_first_name, contact_middle_name, contact_last_name, vendor_store_name, vendor_url,
			vendor_zip, vendor_address_1, vendor_city, vendor_state, vendor_country, contact_email, vendor_phone, contact_fax from #__{vm}_vendor order by vendor_id asc");
		while($db->next_record()) { // it can exist more than just 1 vendor
			$vendor_id = $db->f('vendor_id');
			$vendor_name = ($db->f('contact_title')?$db->f('contact_title')." ":"")
				. ($db->f('contact_first_name')?$db->f('contact_first_name')." ":"")
				. ($db->f('contact_middle_name')?$db->f('contact_middle_name')." ":"")
				. ($db->f('contact_last_name')?$db->f('contact_last_name'):"");
			$vendor_store_name = $db->f('vendor_store_name');
			$vendor_url = $db->f('vendor_url');
			$vendor_postal_code = $db->f('vendor_zip');
			$vendor_street_address = $db->f('vendor_address_1');
			$vendor_locality = $db->f('vendor_city');
			$vendor_region = $db->f('vendor_state');
			$vendor_country_name = $db->f('vendor_country');
			$vendor_email = $db->f('contact_email');
			$vendor_phone = $db->f('vendor_phone');
			$vendor_fax = $db->f('contact_fax');
		
			// define ontology
			if($vendor_id == 1) { // store owner ontology applies to main vendor
				$rdf .= "  <!-- Ontology -->\n";
				$rdf .= "  <owl:Ontology rdf:about=\"\">\n";
				$rdf .= "    <dc:creator rdf:datatype=\"".$namespaces['xsd']."string\">$vendor_name</dc:creator>\n";
				$rdf .= "    <owl:imports rdf:resource=\"".substr($namespaces['gr'], 0, -1)."\"/>\n";
				$rdf .= "    <rdfs:label rdf:datatype=\"".$namespaces['xsd']."string\">RDF/XML data for $vendor_store_name, based on http://purl.org/goodrelations/</rdfs:label>\n";
				$rdf .= "  </owl:Ontology>\n\n";
			}
			
			// store owner business entit(y|ies)
			$rdf .= "  <!-- Store owner and vendors -->\n";
			$rdf .= "  <gr:BusinessEntity rdf:ID=\"Vendor_$vendor_id\">\n";
			$rdf .= "    <gr:legalName rdf:datatype=\"".$namespaces['xsd']."string\">$vendor_store_name</gr:legalName>\n";
			if(!empty($vendor_url))
			$rdf .= "    <rdfs:seeAlso rdf:resource=\"$vendor_url\"/>\n";
			$rdf .= "    <vcard:adr>\n";
			$rdf .= "      <vcard:Address rdf:ID=\"Address_$vendor_id\">\n";
			if(!empty($vendor_postal_code))
			$rdf .= "        <vcard:postal-code xml:lang=\"en\">$vendor_postal_code</vcard:postal-code>\n";
			if(!empty($vendor_street_address))
			$rdf .= "        <vcard:street-address xml:lang=\"en\">$vendor_street_address</vcard:street-address>\n";
			if(!empty($vendor_locality))
			$rdf .= "        <vcard:locality xml:lang=\"en\">$vendor_locality</vcard:locality>\n";
			if(!empty($vendor_region))
			$rdf .= "        <vcard:region xml:lang=\"en\">$vendor_region</vcard:region>\n";
			if(!empty($vendor_country_name))
			$rdf .= "        <vcard:country-name xml:lang=\"en\">$vendor_country_name</vcard:country-name>\n";
			$rdf .= "      </vcard:Address>\n";
			$rdf .= "    </vcard:adr>\n";
			if(!empty($vendor_url))
			$rdf .= "    <vcard:url rdf:resource=\"".$vendor_url."\"/>\n";
			if(!empty($vendor_email))
			$rdf .= "    <vcard:email>".$vendor_email."</vcard:email>\n";
			if(!empty($vendor_phone))
			$rdf .= "    <vcard:tel>".$vendor_phone."</vcard:tel>\n";
			if(!empty($vendor_fax))
			$rdf .= "    <vcard:fax>".$vendor_fax."</vcard:fax>\n";
			
			$db2->query("select t1.product_id, t1.product_available_date, t2.category_id"
					. " from #__{vm}_product t1"
					. " left join #__{vm}_product_category_xref t2 on t1.product_id=t2.product_id"
					. " where t1.product_publish='Y' and t1.vendor_id='$vendor_id'"
					. " group by t1.product_sku"
					);
			while($db2->next_record()) {
				$p_id = $db2->f('product_id');
				$p_available_date = $db2->f('product_available_date');
				$p_validity = intval($p_available_date) + $this->offering_validity*86400;
				$p_cid = intval($db2->f('category_id'));
			
				// if product is not a parent product, then category id is zero - take the category id of the parent product
				if($p_cid == 0) {
					$product_parent_id = $psp->get_field($p_id, 'product_parent_id');
					if($product_parent_id > 0) {
						$db3->query("select category_id from #__{vm}_product_category_xref where product_id='".$product_parent_id."'");
						if($db3->next_record())
							$p_cid = intval($db3->f('category_id'));
					}
				}
				
			// skip, if:
			// * product id is NOT valid
			// * the category id of the product IS zero
			// * the product offering is not yet available (DO NOT CHECK .. show also expired product offerings)
			// * the product offering has expired (DO NOT CHECK .. show also expired product offerings)
				if(!$p_id)// || $p_cid==0 || intval($p_available_date)>time() || ($this->offering_validity>0 && $p_validity<time()))
					continue;
				$rdf .= "    <gr:offers rdf:resource=\"#Offering_$p_id\"/>\n";
			}
			
			$rdf .= "  </gr:BusinessEntity>\n\n";
		}
		
		// custom object properties
		$this->object_properties = array("Weight", "Length", "Width", "Height");
		
		// object properties
		$rdf .= "  <!-- Object Properties -->\n";
		if(!empty($this->object_properties))
		foreach ($this->object_properties as $object_property) {
			$rdf .= "  <owl:ObjectProperty rdf:ID=\"hasProduct$object_property\">\n";
			$rdf .= "    <rdfs:label rdf:datatype=\"".$namespaces['xsd']."string\">Product ".$object_property." Property</rdfs:label>\n";
			$rdf .= "    <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">This property specifies the ".$object_property." of a Product or Service.</rdfs:comment>\n";
			$rdf .= "    <rdfs:subPropertyOf rdf:resource=\"".$namespaces['gr']."quantitativeProductOrServiceProperty\"/>\n";
			$rdf .= "    <rdfs:domain rdf:resource=\"".$namespaces['gr']."ProductOrService\"/>\n";
			$rdf .= "    <rdfs:range rdf:resource=\"".$namespaces['gr']."QuantitativeValueFloat\"/>\n";
			$rdf .= "  </owl:ObjectProperty>\n";
		}
		$rdf .= "\n";
		
		// manufacturers
		$rdf .= "  <!-- Manufacturers -->\n";
		$db->query("select manufacturer_id, mf_name, mf_desc, mf_url from #__{vm}_manufacturer");
		while($db->next_record()) {
			$mf_id = intval($db->f('manufacturer_id'));
			$mf_name = $db->f('mf_name');
			$mf_desc = strip_tags($db->f('mf_desc'));
			$mf_url = $db->f('mf_url');
			
			$rdf .= "  <gr:BusinessEntity rdf:ID=\"Manufacturer_$mf_id\">\n";
			$rdf .= "    <gr:legalName rdf:datatype=\"".$namespaces['xsd']."string\">$mf_name</gr:legalName>\n";
			if(!empty($mf_url))
			$rdf .= "    <rdfs:seeAlso rdf:resource=\"$mf_url\"/>\n";
			if(!empty($mf_desc))
			$rdf .= "    <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">$mf_desc</rdfs:comment>\n";
			$rdf .= "  </gr:BusinessEntity>\n";
		}
		$rdf .= "\n";
		
		// categories
		$rdf .= "  <!-- Categories -->\n";
		$db->query("select t1.category_id, t1.category_name, t1.category_description, t2.category_parent_id from #__{vm}_category t1, #__{vm}_category_xref t2 where t1.category_publish='Y' and t1.category_id=t2.category_child_id");
		while($db->next_record()) {
			$c_id = intval($db->f('category_id'));
			$c_name = $db->f('category_name');
			$c_desc = strip_tags($db->f('category_description'));
			$c_pid = intval($db->f('category_parent_id'));
			
			$rdf .= "  <owl:Class rdf:ID=\"Category_$c_id\">\n";
			if($c_pid == 0) // must be a super category
				$rdf .= "    <rdfs:subClassOf rdf:resource=\"".$namespaces['gr']."ProductOrService\"/>\n";
			else
				$rdf .= "    <rdfs:subClassOf rdf:resource=\"#Category_$c_pid\"/>\n";
			$rdf .= "    <rdfs:label rdf:datatype=\"".$namespaces['xsd']."string\">$c_name (Catalog Group / Category)</rdfs:label>\n";
			if(!empty($c_desc))
			$rdf .= "    <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">$c_desc</rdfs:comment>\n";
			$rdf .= "  </owl:Class>\n";
		}
		$rdf .= "\n";
		
		// warranty promises
		$rdf .= "  <!-- Warranty Promises -->\n";
		if(!empty($warranty_scope))
		foreach($warranty_scope as $key => $value) {
			$rdf .= "  <gr:WarrantyPromise rdf:ID=\"WarrantyPromise_$key\">\n";
			$rdf .= "    <rdfs:label rdf:datatype=\"".$namespaces['xsd']."string\">Warranty promise for $key</rdfs:label>\n";
			$rdf .= "    <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">Warranty duration of $value months for $key</rdfs:comment>\n";
			$rdf .= "    <gr:durationOfWarrantyInMonths rdf:datatype=\"".$namespaces['xsd']."int\">".$value."</gr:durationOfWarrantyInMonths>\n";
			$rdf .= "    <gr:hasWarrantyScope rdf:resource=\"".$namespaces['gr']."$key\"/>\n";
			$rdf .= "  </gr:WarrantyPromise>\n";
		}
		$rdf .= "\n";
		
		// offerings
		$rdf .= "  <!-- Offerings -->\n";
		$db->query("select t1.product_id, t1.product_name, t1.product_sku, t1.product_s_desc, t1.product_desc, t1.product_weight, t1.product_weight_uom,"
				. " t1.product_length, t1.product_width, t1.product_height, t1.product_lwh_uom, t1.product_url, t1.product_in_stock, t1.product_available_date,"
				. " t2.manufacturer_id, t3.category_id, t1.product_special, t4.start_date, t4.end_date"
				. " from #__{vm}_product t1"
				. " left join #__{vm}_product_mf_xref t2 on t1.product_id=t2.product_id"
				. " left join #__{vm}_product_category_xref t3 on t1.product_id=t3.product_id"
				. " left join #__{vm}_product_discount t4 on t1.product_discount_id=t4.discount_id"
				. " where t1.product_publish='Y'"
				. " group by t1.product_sku"
				);
		while($db->next_record()) {
			$p_id = intval($db->f('product_id'));
			$p_cid = intval($db->f('category_id'));
			$p_mid = intval($db->f('manufacturer_id'));
			$p_name = $db->f('product_name');
			$p_sku = $db->f('product_sku');
			$p_short_desc = strip_tags($db->f('product_s_desc'));
			$p_desc = strip_tags($db->f('product_desc'));
			$p_url = $db->f('product_url');
			$p_amount = floatval($db->f('product_in_stock'));
			//$p_available_date = $db->f('product_available_date'); // difficult to handle with old products
			$p_available_date = time();
			$p_validity = intval($p_available_date) + $this->offering_validity*86400;
			$p_tax = $auth["show_price_including_tax"]?$psp->get_product_taxrate($p_id):0.00;
			
			// if product is not a parent product, then category id is zero - take the category id of the parent product
			if($p_cid == 0) {
				$product_parent_id = $psp->get_field($p_id, 'product_parent_id');
				if($product_parent_id > 0) {
					$db2->query("select category_id from #__{vm}_product_category_xref where product_id='".$product_parent_id."'");
					if($db2->next_record())
						$p_cid = intval($db2->f('category_id'));
				}
			}
			
			// skip, if:
			// * product id is NOT valid
			// * the category id of the product IS zero
			// * the product offering is not yet available (DO NOT CHECK .. show also expired product offerings)
			// * the product offering has expired (DO NOT CHECK .. show also expired product offerings)
			if(!$p_id)// || $p_cid==0 || intval($p_available_date)>time() || ($this->offering_validity>0 && $p_validity<time()))
				continue;
			$rdf .= "  <gr:Offering rdf:ID=\"Offering_$p_id\">\n";
			if(!empty($p_url))
			$rdf .= "    <rdfs:seeAlso rdf:resource=\"$p_url\"/>\n";
			$rdf .= "    <gr:includesObject>\n";
			$rdf .= "      <gr:TypeAndQuantityNode rdf:ID=\"TypeAndQuantityNode_$p_id\">\n";
			$rdf .= "        <gr:amountOfThisGood rdf:datatype=\"".$namespaces['xsd']."float\">"./*$p_amount*/"1.0"."</gr:amountOfThisGood>\n";
			$rdf .= "        <gr:hasUnitOfMeasurement rdf:datatype=\"".$namespaces['xsd']."string\">C62</gr:hasUnitOfMeasurement>\n";
			$rdf .= "        <gr:typeOfGood>\n";
			$rdf .= "          <gr:ProductOrServicesSomeInstancesPlaceholder rdf:ID=\"ProductOrServiceInstance_$p_id\">\n";
			if(!empty($p_cid))
			$rdf .= "            <rdf:type rdf:resource=\"#Category_$p_cid\"/>\n";
			$rdf .= "            <rdfs:label rdf:datatype=\"".$namespaces['xsd']."string\">$p_name (ProductOrServicesSomeInstancesPlaceholder)</rdfs:label>\n";
			if(!empty($p_desc))
				$rdf .= "            <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">$p_desc</rdfs:comment>\n";
			else if(!empty($p_short_desc))
				$rdf .= "            <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">$p_short_desc</rdfs:comment>\n";
			if(!empty($p_url))
			$rdf .= "            <rdfs:seeAlso rdf:resource=\"$p_url\"/>\n";
			// inventory level
			if(!empty($p_amount)):
			$rdf .= "            <gr:hasInventoryLevel>\n";
			$rdf .= "              <gr:QuantitativeValueFloat rdf:ID=\"QuantitativeValueFloat_ProductInventoryLevel_$p_id\">\n";
			$rdf .= "                <rdfs:label rdf:datatype=\"".$namespaces['xsd']."string\">Inventory level of $p_name</rdfs:label>\n";
			$rdf .= "                <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">".number_format($p_amount, 1, '.', '')." pieces of $p_name are on stock</rdfs:comment>\n";
			$rdf .= "                <gr:hasValueFloat rdf:datatype=\"".$namespaces['xsd']."float\">".number_format($p_amount, 1, '.', '')."</gr:hasValueFloat>\n";
			$rdf .= "                <gr:hasUnitOfMeasurement rdf:datatype=\"".$namespaces['xsd']."string\">C62</gr:hasUnitOfMeasurement>\n";
			$rdf .= "              </gr:QuantitativeValueFloat>\n";
			$rdf .= "            </gr:hasInventoryLevel>\n";
			endif;
			
			// object properties for ProductOrServicesSomeInstancesPlaceholder
			foreach($this->object_properties as $object_property) {
				$value = $db->f('product_'.strtolower($object_property));
				$uom = $object_property=="Weight"?$uom_weight:$uom_lwh;//$db->f('product_'.strtolower($object_property).'_uom'):$db->f('product_lwh_uom');
				if(floatval($value) != 0.) {
					$rdf .= "            <self:hasProduct$object_property>\n";
					$rdf .= "              <gr:QuantitativeValueFloat rdf:ID=\"QuantitativeValueFloat_Product".$object_property."_$p_id\">\n";
					$rdf .= "                <rdfs:label rdf:datatype=\"".$namespaces['xsd']."string\">$object_property description for $p_name</rdfs:label>\n";
					$rdf .= "                <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">$p_name has a ".strtolower($object_property)." of ".number_format($value, 2, '.', '')." $uom</rdfs:comment>\n";
					$rdf .= "                <gr:hasValueFloat rdf:datatype=\"".$namespaces['xsd']."float\">".number_format($value, 2, '.', '')."</gr:hasValueFloat>\n";
					$rdf .= "                <gr:hasUnitOfMeasurement rdf:datatype=\"".$namespaces['xsd']."string\">$uom</gr:hasUnitOfMeasurement>\n";
					$rdf .= "              </gr:QuantitativeValueFloat>\n";
					$rdf .= "            </self:hasProduct$object_property>\n";
				}
			}
			
			$rdf .= "            <gr:hasMakeAndModel>\n";
			$rdf .= "              <gr:ProductOrServiceModel rdf:ID=\"ProductOrServiceModel_$p_id\">\n";
			if(!empty($p_cid))
			$rdf .= "                <rdf:type rdf:resource=\"#Category_$p_cid\"/>\n";
			$rdf .= "                <rdfs:label rdf:datatype=\"".$namespaces['xsd']."string\">$p_name (ProductOrServiceModel)</rdfs:label>\n";
			if(!empty($p_desc))
				$rdf .= "                <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">$p_desc</rdfs:comment>\n";
			else if(!empty($p_short_desc))
				$rdf .= "                <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">$p_short_desc</rdfs:comment>\n";
			$rdf .= "                <gr:hasManufacturer rdf:resource=\"#Manufacturer_$p_mid\"/>\n";
			if(!empty($p_url))
			$rdf .= "                <rdfs:seeAlso rdf:resource=\"$p_url\"/>\n";
			
			// object properties for ProductOrServiceModel
			foreach($this->object_properties as $object_property) {
				$value = $db->f('product_'.strtolower($object_property));
				$uom = $object_property=="Weight"?$uom_weight:$uom_lwh;//$db->f('product_'.strtolower($object_property).'_uom'):$db->f('product_lwh_uom');
				if(floatval($value) != 0.) {
					$rdf .= "                <self:hasProduct$object_property rdf:resource=\"#QuantitativeValueFloat_Product".$object_property."_$p_id\"/>\n";
				}
			}
	
			$rdf .= "              </gr:ProductOrServiceModel>\n";
			$rdf .= "            </gr:hasMakeAndModel>\n";
			$rdf .= "          </gr:ProductOrServicesSomeInstancesPlaceholder>\n";
			$rdf .= "        </gr:typeOfGood>\n";
			$rdf .= "      </gr:TypeAndQuantityNode>\n";
			$rdf .= "    </gr:includesObject>\n";
			if($p_available_date <= 0)
				$p_available_date = time(); // set validfrom to now, if no date is available from database
			$rdf .= "    <gr:validFrom rdf:datatype=\"".$namespaces['xsd']."dateTime\">".$this->w3cDate($p_available_date)."</gr:validFrom>\n";
			if($this->offering_validity <= 0)
				$this->offering_validity = 1; // set offering validity to 1 day, if not specified yet
			$rdf .= "    <gr:validThrough rdf:datatype=\"".$namespaces['xsd']."dateTime\">".$this->w3cDate($p_available_date, $this->offering_validity)."</gr:validThrough>\n";
			if(!empty($p_sku))
			$rdf .= "    <gr:hasStockKeepingUnit rdf:datatype=\"".$namespaces['xsd']."string\">$p_sku</gr:hasStockKeepingUnit>\n";
			
			// show price specifications
			if($auth['show_prices']) {
				// price specification
				$p_price_info = $psp->get_price($p_id);
				$p_price = $GLOBALS['CURRENCY']->convert($p_price_info['product_price'], $p_price_info['product_currency'], $GLOBALS['product_currency']);
				$p_price = $p_price*(1+$p_tax);
				
				$db2->query("select price_quantity_start, price_quantity_end from #__{vm}_product_price where product_id='$p_id' and shopper_group_id='".$auth["shopper_group_id"]."'");
				if($db2->next_record()) {
					$price_quantity_start = $db2->f('price_quantity_start');
					$price_quantity_end = $db2->f('price_quantity_end');
				}
				
				$rdf .= "    <gr:hasPriceSpecification>\n";
				$rdf .= "      <gr:UnitPriceSpecification rdf:ID=\"UnitPriceSpecification_$p_id\">\n";
				$rdf .= "        <gr:hasCurrency rdf:datatype=\"".$namespaces['xsd']."string\">".$GLOBALS['product_currency']."</gr:hasCurrency>\n";
				$rdf .= "        <gr:hasCurrencyValue rdf:datatype=\"".$namespaces['xsd']."float\">".number_format($p_price, 2, '.', '')."</gr:hasCurrencyValue>\n";
				$rdf .= "        <gr:hasUnitOfMeasurement rdf:datatype=\"".$namespaces['xsd']."string\">C62</gr:hasUnitOfMeasurement>\n";
				$rdf .= "        <gr:valueAddedTaxIncluded rdf:datatype=\"".$namespaces['xsd']."boolean\">".($auth['show_price_including_tax']?"true":"false")."</gr:valueAddedTaxIncluded>\n";
				if($price_quantity_start>0 || $price_quantity_end>0) {
					$rdf .= "        <gr:hasEligibleQuantity>\n";
					$rdf .= "          <gr:QuantitativeValueInteger rdf:ID=\"QuantitativeValueInteger_EligibleQuantity_$p_id\">\n";
					if($price_quantity_start > 0)
					$rdf .= "            <gr:hasMinValueInteger rdf:datatype=\"".$namespaces['xsd']."int\">$price_quantity_start</gr:hasMinValueInteger>\n";
					if($price_quantity_end > 0)
					$rdf .= "            <gr:hasMaxValueInteger rdf:datatype=\"".$namespaces['xsd']."int\">$price_quantity_end</gr:hasMaxValueInteger>\n";
					$rdf .= "            <gr:hasUnitOfMeasurement rdf:datatype=\"".$namespaces['xsd']."string\">C62</gr:hasUnitOfMeasurement>\n";
					$rdf .= "          </gr:QuantitativeValueInteger>\n";
					$rdf .= "        </gr:hasEligibleQuantity>\n";
				}
				$rdf .= "        <gr:validFrom rdf:datatype=\"".$namespaces['xsd']."dateTime\">".$this->w3cDate($p_available_date)."</gr:validFrom>\n";
				$rdf .= "        <gr:validThrough rdf:datatype=\"".$namespaces['xsd']."dateTime\">".$this->w3cDate($p_available_date, $this->offering_validity)."</gr:validThrough>\n";
				$rdf .= "      </gr:UnitPriceSpecification>\n";
				$rdf .= "    </gr:hasPriceSpecification>\n";
				// show special price specifications
				if($db->f('product_special') == 'Y') {
					// special price
					$special_price_info = $psp->get_discount($p_id);
					// convert currency (but, as a strange fact, virtuemart does not convert between currency values,
					// so we comment it out for the meanwhile)
					//$special_price_info["amount"] = $GLOBALS['CURRENCY']->convert($special_price_info["amount"], $p_price_info['product_currency'], $GLOBALS['product_currency']);
					$start_date = $db->f('start_date');
					$end_date = $db->f('end_date');
					
					// show special price specification if:
					// * the special has already started AND
					//   * we have no $end_date specified OR
					//   * we have an $end_date, but are still in specials duration period OR
					//   * we got a special_validity parameter from the configuration, whose value is still in the specials validity period
					if($start_date < time() && ($end_date == 0 || $end_date > time() || $start_date+$this->special_validity*86400 > time())) {
						// Calculate discount
						if( !empty($special_price_info["amount"])) {
							switch( $special_price_info["is_percent"] ) {
								case 0:
									// If we subtract discounts BEFORE tax
									if( PAYMENT_DISCOUNT_BEFORE == '1' ) {
										// and if our prices are shown with tax
										if( $auth["show_price_including_tax"] == 1) {
											// then we add tax to the (untaxed) discount
											$special_price_info['amount'] += ($p_tax*$special_price_info['amount']);
										} 
										// but if our prices are shown without tax
											// we just leave the (untaxed) discount amount as it is
											
									}
									// But, if we subtract discounts AFTER tax
										// and if our prices are shown with tax
											// we just leave the (untaxed) discount amount as it is
										// but if  prices are shown without tax
											// we just leave the (untaxed) discount amount as it is
											// even though this is not really a good combination of settings

									$p_price -= $special_price_info["amount"];
									break;
								case 1:
									$p_price *= (100 - $special_price_info["amount"])/100;
									break;
							}
							
							$rdf .= "    <gr:hasPriceSpecification>\n";
							$rdf .= "      <gr:UnitPriceSpecification rdf:ID=\"UnitPriceSpecification_".$p_id."_Special\">\n";
							$rdf .= "        <rdfs:comment rdf:datatype=\"".$namespaces['xsd']."string\">Special price offer</rdfs:comment>\n";
							$rdf .= "        <gr:hasCurrency rdf:datatype=\"".$namespaces['xsd']."string\">".$GLOBALS['product_currency']."</gr:hasCurrency>\n";
							$rdf .= "        <gr:hasCurrencyValue rdf:datatype=\"".$namespaces['xsd']."float\">".number_format($p_price, 2, '.', '')."</gr:hasCurrencyValue>\n";
							$rdf .= "        <gr:hasUnitOfMeasurement rdf:datatype=\"".$namespaces['xsd']."string\">C62</gr:hasUnitOfMeasurement>\n";
							if($start_date != 0)
							$rdf .= "        <gr:validFrom rdf:datatype=\"".$namespaces['xsd']."dateTime\">".$this->w3cDate($start_date)."</gr:validFrom>\n";
							if($end_date != 0 && $start_date<$end_date)
							$rdf .= "        <gr:validThrough rdf:datatype=\"".$namespaces['xsd']."dateTime\">".$this->w3cDate($end_date)."</gr:validThrough>\n";
							else if($start_date != 0) // no end date specified, take 1 day per default
							$rdf .= "        <gr:validThrough rdf:datatype=\"".$namespaces['xsd']."dateTime\">".$this->w3cDate($start_date, ($this->special_validity>0)?$this->special_validity:1)."</gr:validThrough>\n";
							$rdf .= "        <gr:valueAddedTaxIncluded rdf:datatype=\"".$namespaces['xsd']."boolean\">".($auth['show_price_including_tax']?"true":"false")."</gr:valueAddedTaxIncluded>\n";
							if($price_quantity_start>0 || $price_quantity_end>0) {
								$rdf .= "        <gr:hasEligibleQuantity>\n";
								$rdf .= "          <gr:QuantitativeValueInteger rdf:ID=\"QuantitativeValueInteger_EligibleQuantity_$p_id\">\n";
								if($price_quantity_start > 0)
								$rdf .= "            <gr:hasMinValueInteger rdf:datatype=\"".$namespaces['xsd']."int\">$price_quantity_start</gr:hasMinValueInteger>\n";
								if($price_quantity_end > 0)
								$rdf .= "            <gr:hasMaxValueInteger rdf:datatype=\"".$namespaces['xsd']."int\">$price_quantity_end</gr:hasMaxValueInteger>\n";
								$rdf .= "            <gr:hasUnitOfMeasurement rdf:datatype=\"".$namespaces['xsd']."string\">C62</gr:hasUnitOfMeasurement>\n";
								$rdf .= "          </gr:QuantitativeValueInteger>\n";
								$rdf .= "        </gr:hasEligibleQuantity>\n";
							}
							$rdf .= "      </gr:UnitPriceSpecification>\n";
							$rdf .= "    </gr:hasPriceSpecification>\n";	
						}
					}
				}
			}
			// offering details got from master data in the backend
			foreach($elements as $element) {
			
				$property = NULL;
				$parameter_array_name = strtolower(ereg_replace('^_', '', ereg_replace('[A-Z]','_\\0',$element)));
				
				switch($element) {
					/*case 'BusinessFunction':
						$property = 'has'.$element;
					break;*/
					case 'BusinessEntityType':
						$property = 'eligibleCustomerTypes';
					break;
					case 'DeliveryMethod':
						$property = 'available'.$element.'s';
					break;
					case 'PaymentMethod':
						$property = 'accepted'.$element.'s';
					break;
					case 'WarrantyScope':
						$property = 'hasWarrantyPromise';
					break;
				}
				
				if($$parameter_array_name != NULL && $property != NULL) {
					if($element == 'WarrantyScope') {
						foreach($$parameter_array_name as $key => $value) {
							$rdf .= "    <gr:$property rdf:resource=\"#WarrantyPromise_$key\"/>\n";
						}
					}
					/*else if($element == 'BusinessFunction') {
						$value = $$parameter_array_name[$element];
						$rdf .= "    <gr:$property rdf:resource=\"".$namespaces['gr']."$value\"/>\n";
					}*/
					else {
						foreach($$parameter_array_name as $key => $value) {
							$rdf .= "    <gr:$property rdf:resource=\"".$namespaces['gr']."$key\"/>\n";
						}
					}
				}
			}
			// business function
			if(($business_function = $this->_params->get('BusinessFunction')) != NULL) {
				$rdf .= "    <gr:hasBusinessFunction rdf:resource=\"".$namespaces['gr'].$business_function."\"/>\n";
			}
			// eligible regions
			if(($eligible_regions = $this->_params->get('eligibleRegions')) != NULL) {
				$regions_array = explode(',', $eligible_regions);
				if(sizeof($regions_array)>0)
				foreach($regions_array as $region_code) {
				    if(trim($region_code)!="")
					   $rdf .= "    <gr:eligibleRegions rdf:datatype=\"".$namespaces['xsd']."string\">".trim($region_code)."</gr:eligibleRegions>\n";
				}
			}
			
			$rdf .= "  </gr:Offering>\n";
		}
		
		$rdf .= "\n</rdf:RDF>\n";
		
		$rdf = ereg_replace("&[^amp;]", "&amp;", $rdf);
		
		return $rdf;
	}
	
	// redirection
	function redirectRDF() {
		if ($this->filename=="") {
			$this->filename = $this->_generateFilename();
		}
		$this->_redirect($this->filename);
	}
	
	// print the filename of the OWL file
	function showRDF() {
		if ($this->filename=="") {
			$this->filename = $this->_generateFilename();
		}
		echo $this->filename;
	}
}
?>